# coding: utf-8
import numpy as np
import cPickle
import os

#Class for Item similarity based Recommender System model
class item_cf_recommender():
    def __init__(self):
        self.train_data = None
        self.user_id = None
        self.item_id = None
        
        self.similarity_matrix = None
        self.item_similarity_recommendations = None

     
    #item based CF is based on KNN, not model training
    def fit(self, train_data, user_id, item_id):
        self.train_data = train_data
        self.user_id = user_id
        self.item_id = item_id

        all_items = self.get_all_items_train_data()
        self.construct_similarity_matrix(all_items)
        
        
    #Get unique items (songs) corresponding to a given user
    #用户打过分的item，计算这些item与库中item的相似度
    def get_user_items(self, user):
        items = self.train_data[self.train_data[self.user_id] == user]
        user_items = list(items[self.item_id].unique())
        
        return user_items
    
    #Get unique items (songs) corresponding to a given user
    #用户打过分的item，计算这些item与库中item的相似度
    def get_user_items_offline(self, user):
        items = self.train_data[self.train_data[self.user_id] == user]
        user_items = list(items[self.item_id].unique())
        
        return user_items
        
    #Get unique users for a given item (song)
    #给定item的用户，用于得到item的特征向量（每个item用给这个item打过分的用户的打分表示）
    def get_item_users(self, item):
        users = self.train_data[self.train_data[self.item_id] == item]
        item_users = set(users[self.user_id].unique())
            
        return item_users
        
    #Get unique items in the training data
    #候选item
    def get_all_items_train_data(self):
        all_items = list(self.train_data[self.item_id].unique())
            
        return all_items

        #Construct similarity matrix
    #库中所有item之间的相似度
    def construct_similarity_matrix(self, all_items):
        ###############################################
        #Initialize the item similarity matrix of size 
        #len(all_items) X len(all_items)
        # e.g. 1000 * 1000
        ###############################################
        n_items = len(all_items)
        similarity_matrix = np.matrix(np.zeros(shape=(n_items, n_items)), float)
           
        #############################################################
        #Calculate similarity between user items (i.e songs that user played) and all unique items
        #in the training data
        #############################################################
        for i in range(0,n_items):  # all items
            #得到item i的特征表示：Calculate unique users of item i
            users_i = self.get_item_users(all_items[i])
            similarity_matrix[i,i] = 1.0
            if(i % 10 == 0):
                print("")
            if i%50 == 0:
                  print "i=:%d " % (i)
          
            
            for j in range(i+1,n_items):   #items by user    
                    
                #得到item j的特征表示：Get unique users of item j
                users_j = self.get_item_users(all_items[j])
                    
                #Calculate intersection of listeners of songs i and j
                #计算item i与item j的相似度
                users_intersection = users_i.intersection(users_j)
                
                #Calculate cooccurence_matrix[i,j] as Jaccard Index
                if len(users_intersection) != 0:
                    #Calculate union of listeners of songs i and j
                    users_union = users_i.union(users_j)
                    similarity_matrix[j,i] = float(len(users_intersection))/float(len(users_union))
                    
                else:
                    similarity_matrix[j,i] = 0
                    
                similarity_matrix[i,j] = similarity_matrix[j,i]
                    
        
        
        cPickle.dump(self.similarity_matrix, open("items_similarity.pkl", 'wb'))
        return similarity_matrix
    
    #Construct similarity matrix
    #用户打过分的item与库中所有item的相似度
    def construct_similarity_matrix_one_user(self, user_items, all_items):
        ###############################################
        #Initialize the item similarity matrix of size 
        #len(user_items) X len(all_items)
        # e.g. 100 * 1000
        ###############################################
        similarity_matrix = np.matrix(np.zeros(shape=(len(user_items), len(all_items))), float)
           
        #############################################################
        #Calculate similarity between user items (i.e songs that user played) and all unique items
        #in the training data
        #############################################################
        for i in range(0,len(all_items)):  # all items
            #得到item i的特征表示：Calculate unique users of item i
            users_i = self.get_item_users(all_items[i])
            
            for j in range(0,len(user_items)):   #items by user    
                    
                #得到item j的特征表示：Get unique users of item j
                users_j = self.get_item_users(user_items[j])
                    
                #Calculate intersection of listeners of songs i and j
                #计算item i与item j的相似度
                users_intersection = users_i.intersection(users_j)
                
                #Calculate cooccurence_matrix[i,j] as Jaccard Index
                if len(users_intersection) != 0:
                    #Calculate union of listeners of songs i and j
                    users_union = users_i.union(users_j)
                    similarity_matrix[j,i] = float(len(users_intersection))/float(len(users_union))
                else:
                    similarity_matrix[j,i] = 0
                    
        
        return similarity_matrix

    
    #Use the similarity matrix to make top recommendations
    def generate_top_recommendations(self, user, similarity_matrix, all_items, user_items):
        print("Non zero values in similarity_matrix :%d" % np.count_nonzero(similarity_matrix))
        
        #Calculate a weighted average of the scores in similarity_matrix matrix for all user items.
        user_sim_scores = similarity_matrix.sum(axis=0)/float(similarity_matrix.shape[0])
        user_sim_scores = np.array(user_sim_scores)[0].tolist()
 
        #Sort the indices of user_sim_scores based upon their value
        #Also maintain the corresponding score
        sort_index = sorted(((e,i) for i,e in enumerate(list(user_sim_scores))), reverse=True)
    
        #Create a dataframe from the following
        columns = ['user_id', 'item', 'score', 'rank']
        #index = np.arange(1) # array of numbers for the number of samples
        df = pandas.DataFrame(columns=columns)
         
        #Fill the dataframe with top 20 item based recommendations
        rank = 1 
        for i in range(0,len(sort_index)):
            if ~np.isnan(sort_index[i][0]) and all_items[sort_index[i][1]] not in user_items and rank <= 20:
                df.loc[len(df)]=[user,all_items[sort_index[i][1]],sort_index[i][0],rank]
                rank = rank+1
        
        #Handle the case where there are no recommendations
        if df.shape[0] == 0:
            print("The current user has no songs for training the item similarity based recommendation model.")
            return -1
        else:
            return df

    #Use the item similarity based recommender system model to
    #make recommendations
    #对外API，为用户user提供推荐item候选集合
    def predict(self, user):
        
        ########################################
        #A. Get all unique items for this user
        ########################################
        user_items = self.get_user_items(user)    
            
        print("No. of unique items for the user: %d" % len(user_items))
        
        ######################################################
        #B. Get all unique items in the training data
        ######################################################
        all_items = self.get_all_items_train_data()
        
        print("no. of unique items in the training set: %d" % len(all_items))
         
        ###############################################
        #C. Construct item similarity_matrix matrix of size 
        #len(user_items) X len(all_items)
        ###############################################
        similarity_matrix = self.construct_similarity_matrix(user_items, all_items)
        
        #######################################################
        #D. Use the cooccurence matrix to make recommendations
        #######################################################
        df_recommendations = self.generate_top_recommendations(user, similarity_matrix, all_items, user_items)
                
        return df_recommendations    
     